package com.chenfan.mcn.component;

import com.chenfan.ccp.plug.log.util.TraceIdUtil;
import com.chenfan.ccp.util.start.ApplicationContextUtil;
import com.chenfan.common.exception.BusinessException;
import com.chenfan.common.exception.ResultState;
import com.chenfan.mcn.annotations.ActionTracing;
import com.chenfan.mcn.config.UserVoContextHolder;
import com.chenfan.mcn.constant.DingTalkTemplateCst;
import com.chenfan.mcn.exception.McnErrorCode;
import com.dingtalk.api.request.OapiRobotSendRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * @author wulg
 * @date 2023/8/31
 */
@Slf4j
@Configuration
@ConditionalOnProperty(name="alarm.dingtalk.enabled", havingValue = "true")
public class EnhancedHandlerExceptionResolver implements BeanPostProcessor {

    @Autowired
    private DingTalkAlarmSender alarmSender;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof HandlerExceptionResolverComposite) {
            HandlerExceptionResolverComposite composite = (HandlerExceptionResolverComposite)bean;
            return new HandlerExceptionResolverComposite() {

                private static final String UNKOWN = "未知";

                private static final String DEFAULT_TITLE = "异常告警";

                public void HandlerExceptionResolverComposite() {
                    this.setExceptionResolvers(composite.getExceptionResolvers());
                    this.setOrder(composite.getOrder());
                }

                @Override
                @Nullable
                public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
                    try {
                        if(Objects.nonNull(ex)) {
                            this.exceptionAlarm(request, response, handler, ex);
                        }
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                    return composite.resolveException(request, response, handler, ex);
                }

                /**
                 * 异常告警
                 * @param request
                 * @param response
                 * @param handler
                 * @param ex
                 */
                private void exceptionAlarm(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
                    //排除静态资源
                    if(handler != null && handler instanceof HandlerMethod) {
                        HandlerMethod handlerMethod = ((HandlerMethod) handler);
                        //排除BasicErrorController
                        if (BasicErrorController.class.isAssignableFrom(handlerMethod.getBeanType())) {
                            return;
                        }
                        ActionTracing actionTracing = handlerMethod.getMethodAnnotation(ActionTracing.class);
                        ResultState state = null;
                        if(ex instanceof BusinessException) {
                            state = ((BusinessException)ex).getState();
                            McnErrorCode mcnErrorCode = McnErrorCode.toEnum(state.code());
                            if(!this.anySceneAlarm(actionTracing) && Objects.nonNull(mcnErrorCode) && !mcnErrorCode.isMustAlarm()) {
                                return;
                            }
                        } else if(ex instanceof MethodArgumentNotValidException && !this.anySceneAlarm(actionTracing)) {
                            return;
                        } else if(ex instanceof ClientAbortException) {
                            return;
                        }
                        String traceId = TraceIdUtil.getTraceId();
                        OapiRobotSendRequest sendRequest = new OapiRobotSendRequest();
                        sendRequest.setMsgtype("markdown");
                        OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
                        markdown.setTitle(this.getTitle(actionTracing));
                        markdown.setText(String.format(DingTalkTemplateCst.API, ApplicationContextUtil.getProfile(), ApplicationContextUtil.getServiceName(),
                                request.getRequestURI(), UserVoContextHolder.getDefaultUserName(),
                                traceId, alarmSender.getSkipUrl(traceId), getErrorCode(state), getErrorMsg(state, ex))
                        );
                        sendRequest.setMarkdown(markdown);
                        OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
                        at.setIsAtAll(true);
                        sendRequest.setAt(at);
                        alarmSender.send(sendRequest);
                    }
                }

                /**
                 * 获取标题
                 * @param actionTracing
                 * @return
                 */
                private String getTitle(ActionTracing actionTracing) {
                    if(Objects.isNull(actionTracing)) {
                        return DEFAULT_TITLE;
                    }
                    return String.format(DingTalkTemplateCst.TITLE, DEFAULT_TITLE, actionTracing.value());
                }


                /**
                 * 获取错误码
                 * @param state
                 * @return
                 */
                private String getErrorCode(ResultState state) {
                    if(Objects.nonNull(state)) {
                        return String.valueOf(state.code());
                    }
                    return UNKOWN;
                }
                /**
                 * 获取错误信息
                 * @param state
                 * @return
                 */
                private String getErrorMsg(ResultState state, Exception ex) {
                    if(Objects.nonNull(state)) {
                        return state.message();
                    }
                    return ex.getMessage();
                }

                /**
                 * 是否任何场景都告警
                 * @param actionTracing
                 * @return
                 */
                private boolean anySceneAlarm(ActionTracing actionTracing) {
                    if(Objects.isNull(actionTracing)) {
                        return false;
                    }
                    return actionTracing.allAlarm();
                }

            };
        }
        return bean;
    }
}
